"use strict";
let n = 0;
class Vec3 {
    static set(out, x, y, z) {
        out.x = x;
        out.y = y;
        out.z = z;
        return out;
    }
    static add(out, a, b) {
        out.x = a.x + b.x;
        out.y = a.y + b.y;
        out.z = a.z + b.z;
        return out;
    }
    static multiplyScalar(out, a, b) {
        out.x = a.x * b;
        out.y = a.y * b;
        out.z = a.z * b;
        return out;
    }
    static scaleAndAdd(out, a, b, scale) {
        out.x = a.x + b.x * scale;
        out.y = a.y + b.y * scale;
        out.z = a.z + b.z * scale;
        return out;
    }
    static copy(out, a) {
        out.x = a.x;
        out.y = a.y;
        out.z = a.z;
        return out;
    }
    constructor(x, y, z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}
class Particle {
    constructor(particleSystem) {
        this.particleSystem = particleSystem;
        this.position = new Vec3(0, 0, 0);
        this.velocity = new Vec3(0, 0, 0);
        this.animatedVelocity = new Vec3(0, 0, 0);
        this.ultimateVelocity = new Vec3(0, 0, 0);
        this.startSize = new Vec3(0, 0, 0);
        this.size = new Vec3(0, 0, 0);
        this.randomSeed = 0; // uint
        this.remainingLifetime = 0.2;
        this.loopCount = 0;
        this.lastLoop = 0;
        this.trailDelay = 0;
        this.startLifetime = 1;
        this.emitAccumulator0 = 0.0;
        this.emitAccumulator1 = 0.0;
        this.frameIndex = 0.0;
        this.startRow = 0;
    }
}
// let Mode: {
//   Constant: number,
//   Curve: number,
//   TwoCurves: number,
//   TwoConstants: number
// } = {
//   Constant: 0,
//   Curve: 1,
//   TwoCurves: 2,
//   TwoConstants: 3,
// };
class Mode {
    constructor() {
        this.TwoCurves = 2;
        this.TwoConstants = 3;
    }
}
Mode.Constant = 0;
Mode.Curve = 1;
function repeat(t, length) {
    return t - Math.floor(t / length) * length;
}
function wrapRepeat(time, prevTime, nextTime) {
    return prevTime + repeat(time - prevTime, nextTime - prevTime);
}
function binarySearchEpsilon(array, value, EPSILON = 1e-6) {
    let low = 0;
    let high = array.length - 1;
    let middle = high >>> 1;
    for (; low <= high; middle = (low + high) >>> 1) {
        n++;
        const test = array[middle];
        if (test > (value + EPSILON)) {
            high = middle - 1;
        }
        else if (test < (value - EPSILON)) {
            low = middle + 1;
        }
        else {
            return middle;
        }
    }
    return ~low;
}
function bezierInterpolate(p0, p1, p2, p3, t) {
    n++;
    const u = 1 - t;
    const coeff0 = u * u * u;
    const coeff1 = 3 * u * u * t;
    const coeff2 = 3 * u * t * t;
    const coeff3 = t * t * t;
    return coeff0 * p0 + coeff1 * p1 + coeff2 * p2 + coeff3 * p3;
}
class RealKeyframeValue {
    constructor(a, b, c, d, e) {
        this.value = 0.0;
        this.rightTangent = 0.0;
        this.rightTangentWeight = 0.0;
        this.leftTangent = 0.0;
        this.leftTangentWeight = 0.0;
        this.value = a;
        this.rightTangent = b;
        this.rightTangentWeight = c;
        this.leftTangent = d;
        this.leftTangentWeight = e;
    }
}
function evalBetweenTwoKeyFrames(prevTime, prevValue, nextTime, nextValue, ratio) {
    const dt = nextTime - prevTime;
    const ONE_THIRD = 1.0 / 3.0;
    const prevTangentWeightEnabled = false;
    const nextTangentWeightEnabled = false;
    const prevTangent = prevValue.rightTangent;
    const prevTangentWeightSpecified = prevValue.rightTangentWeight;
    // const {
    //   rightTangent: prevTangent,
    //   rightTangentWeight: prevTangentWeightSpecified,
    // } = prevValue;
    const nextTangent = nextValue.leftTangent;
    const nextTangentWeightSpecified = nextValue.leftTangentWeight;
    // const {
    //   leftTangent: nextTangent,
    //   leftTangentWeight: nextTangentWeightSpecified,
    // } = nextValue;
    if (!prevTangentWeightEnabled && !nextTangentWeightEnabled) {
        const p1 = prevValue.value + ONE_THIRD * prevTangent * dt;
        const p2 = nextValue.value - ONE_THIRD * nextTangent * dt;
        return bezierInterpolate(prevValue.value, p1, p2, nextValue.value, ratio);
    }
    return 0;
}
function assertIsTrue(expr, message) {
    if (!expr) {
        throw new Error(`Assertion failed:`);
    }
}
class RealCurve {
    constructor() {
        this.value = 0.0;
        this.rightTangent = 0.0;
        this.rightTangentWeight = 0.0;
        this.leftTangent = 0.0;
        this.leftTangentWeight = 0.0;
        this._times = [0.1111111, 0.555555555, 0.999999999];
        this._values = [
            new RealKeyframeValue(0.2, 0.7, 0, 0.3, 0),
            new RealKeyframeValue(0.3, 0.4, 0, 0.2, 0),
            new RealKeyframeValue(0.4, 0.5, 0, 0.6, 0)
        ];
    }
    evaluate(time) {
        const times = this._times;
        const values = this._values;
        // const {
        //   _times: times,
        //   _values: values,
        // } = this;
        const nFrames = times.length;
        const firstTime = times[0];
        const lastTime = times[nFrames - 1];
        if (time < firstTime) {
            const preValue = values[0];
            time = wrapRepeat(time, firstTime, lastTime);
        }
        const index = binarySearchEpsilon(times, time);
        if (index >= 0) {
            return values[index].value;
        }
        const iNext = ~index;
        // assertIsTrue(iNext !== 0 && iNext !== nFrames && nFrames > 1);
        const iPre = iNext - 1;
        const preTime = times[iPre];
        const preValue = values[iPre];
        const nextTime = times[iNext];
        const nextValue = values[iNext];
        // assertIsTrue(nextTime > time && time > preTime);
        const dt = nextTime - preTime;
        const ratio = (time - preTime) / dt;
        return evalBetweenTwoKeyFrames(preTime, preValue, nextTime, nextValue, ratio);
    }
}
class CurveRange {
    constructor(thismode) {
        this.mode = Mode.Constant;
        this.spline = new RealCurve();
        this.constant = 1;
        this.multiplier = 1;
        this.mode = thismode;
    }
    evaluate(time) {
        switch (this.mode) {
            case Mode.Constant:
                return this.constant;
            case Mode.Curve:
                return this.spline.evaluate(time) * this.multiplier;
            default:
        }
    }
}
class SizeModule {
    constructor() {
        this.size = new CurveRange(Mode.Curve);
        this.x = new CurveRange(Mode.Curve);
        this.y = new CurveRange(Mode.Curve);
        this.z = new CurveRange(Mode.Curve);
    }
    animate(particle, dt) {
        Vec3.multiplyScalar(particle.size, particle.startSize, this.size.evaluate(1 - particle.remainingLifetime / particle.startLifetime));
    }
}
class VelocityModule {
    constructor() {
        this.x = new CurveRange(Mode.Curve);
        this.y = new CurveRange(Mode.Curve);
        this.z = new CurveRange(Mode.Curve);
        this.speedModifier = new CurveRange(Mode.Constant);
        this.space = 0;
        this._temp_v3 = new Vec3(0, 0, 0);
        this.speedModifier.constant = 1;
        this.needTransform = false;
    }
    animate(p, dt) {
        const normalizedTime = 1 - p.remainingLifetime / p.startLifetime;
        const vel = Vec3.set(this._temp_v3, this.x.evaluate(normalizedTime), this.y.evaluate(normalizedTime), this.z.evaluate(normalizedTime));
        if (this.needTransform) {
        }
        Vec3.add(p.animatedVelocity, p.animatedVelocity, vel);
        Vec3.add(p.ultimateVelocity, p.velocity, p.animatedVelocity);
        Vec3.multiplyScalar(p.ultimateVelocity, p.ultimateVelocity, this.speedModifier.evaluate(1 - p.remainingLifetime / p.startLifetime));
    }
}
class ParticleSystemRenderCPU {
    constructor() {
        let size = 50;
        this._particles = new Array(size);
        for (let i = 0; i < size; i++) {
            this._particles[i] = new Particle(this);
        }
        this._sizeModule = new SizeModule();
        this._velocityModule = new VelocityModule();
    }
    UpdateParticles(dt) {
        for (let i = 0; i < this._particles.length; ++i) {
            const p = this._particles[i];
            Vec3.set(p.animatedVelocity, 0, 0, 0);
            Vec3.copy(p.ultimateVelocity, p.velocity);
            this._sizeModule.animate(p, dt);
            this._velocityModule.animate(p, dt);
            Vec3.scaleAndAdd(p.position, p.position, p.ultimateVelocity, dt); // apply velocity.
        }
    }
}
function RunCocos() {
    let systems = new Array(18);
    for (let i = 0; i < 18; i++) {
        systems[i] = new ParticleSystemRenderCPU();
    }
    let start = Date.now();
    for (let j = 0; j < 600; j++) {
        for (let i = 0; i < 18; i++) {
            systems[i].UpdateParticles(0.5);
        }
    }
    let end = Date.now();
    let time = (end - start);
    console.log("Cocos - RunCocos:\t" + String(time) + "\tms");
    return time;
}
RunCocos();
// let runner1 = new BenchmarkRunner("Cocos - RunCocos", RunCocos);
// runner1.run();
